// ==UserScript==
// @name         aliexpress reviews
// @namespace    hth
// @version      0.7
// @description  get aliexpress reviews
// @author       hth
// @updateURL    https://gitee.com/hthgit/tampermonkey/raw/master/lib/aliexpress_reviews.user.js
// @match        *://*.amazon.com/*
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// ==/UserScript==

(function () {
    
    
    var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})}

    ;(function ($) {
        $.fn.wordExport = function (exPortData) {

            _initData(exPortData);
            var options = {
                maxWidth: 1000
            };
            // Clone selected element before manipulating it
            var markup = $(this).clone();

            // Remove hidden elements from the output
            markup.each(function () {
                var self = $(this);
                if (self.is(':hidden'))
                    self.remove();
            });

            // Embed all images using Data URLs
            var images = Array();
            var img = markup.find('img');
            for (var i = 0; i < img.length; i++) {
                // Calculate dimensions of output image
                var w = Math.min(img[i].width, options.maxWidth);
                var h = img[i].height * (w / img[i].width);
                // Create canvas for converting image to data URL
                var canvas = document.createElement("CANVAS");
                canvas.width = w;
                canvas.height = h;
                // Draw image to canvas
                var context = canvas.getContext('2d');
                context.drawImage(img[i], 0, 0, w, h);
                // Get data URL encoding of image
                var uri = canvas.toDataURL("image/png");
                $(img[i]).attr("src", img[i].src);
                img[i].width = w;
                img[i].height = h;
                // Save encoded image to array
                images[i] = {
                    type: uri.substring(uri.indexOf(":") + 1, uri.indexOf(";")),
                    encoding: uri.substring(uri.indexOf(";") + 1, uri.indexOf(",")),
                    location: $(img[i]).attr("src"),
                    data: uri.substring(uri.indexOf(",") + 1)
                };
            }

            // Prepare bottom of mhtml file with image data
            var mhtmlBottom = "\n";
            for (var i = 0; i < images.length; i++) {
                mhtmlBottom += "--NEXT.ITEM-BOUNDARY\n";
                mhtmlBottom += "Content-Location: " + images[i].location + "\n";
                mhtmlBottom += "Content-Type: " + images[i].type + "\n";
                mhtmlBottom += "Content-Transfer-Encoding: " + images[i].encoding + "\n\n";
                mhtmlBottom += images[i].data + "\n\n";
            }
            mhtmlBottom += "--NEXT.ITEM-BOUNDARY--";
            var fileContent = getModelHtml(markup.html(), mhtmlBottom,exPortData);
            var blob = new Blob([fileContent], {
                type: "application/msword;charset=utf-8"
            });
            saveAs(blob, exPortData.fileName + exPortData.fileType);
        };
        function _initData(exPortData) {
            exPortData.fileName = typeof(exPortData.fileName) !== 'undefined' ? exPortData.fileName : "jQuery-Word-Export";
            exPortData.fileType=typeof (exPortData.fileType)!=="undefined"?exPortData.fileType:".doc";
        }
        function getModelHtml(mhtml, mhtmlBottom, exPortData) {
            var styles,updateStyles,header,footer="";
            if(exPortData.styles){
                styles=exPortData.styles.html();
            }
            if(exPortData.updateExportCss){
                updateStyles=exPortData.updateExportCss.html();
            }
            if(exPortData.isHeader){
                header="<div style=\"mso-element:header;padding-bottom:20px;\" id=\"h1\">\n" +
                    "<p class=\"MsoHeader\">"+exPortData.header+"</p>\n" +
                    "</div>";
            }
            if(exPortData.isFooter){
                footer="<div style=\"mso-element:footer;text-align: center;\" id=\"f1\">\n" +
                    "<span style=\"mso-field-code: PAGE \"></span>/<span style=\"mso-field-code:NUMPAGES\"></span>\n" +
                    "</div>";
            }
            return `
                <!DOCTYPE html>
                <html>
                <head>
                    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
                    <style type="text/css">
                        table#hrdftrtbl {
                            margin: 0in 0in 0in 900in;
                            width: 1px;
                            height: 1px;
                            overflow: hidden;
                        }
                
                        p.MsoHeader {
                            font-size: 20px;
                            font-weight: bold;
                            text-align: center;
                        }
                
                        p.MsoFooter, li.MsoFooter, div.MsoFooter {
                            margin: 0in;
                            mso-pagination: widow-orphan;
                            tab-stops: center 3.0in right 6.0in;
                            font-size: 20pt;
                            font-weight: bold
                        }
                
                        <!-- /*FontDefinitions*/
                        @page WordSection {
                            size: 1260px 768px;
                            margin: 30px;
                            mso-header-margin: 20px;
                            mso-footer-margin: 20px;
                            mso-header: h1;
                            mso-footer: f1;
                        }
                
                        div.WordSection {
                            page: WordSection;
                        }
                
                        -->
                        ${styles}
                        ${updateStyles}
                    </style>
                </head>
                <body lang=ZH-CN style="tab-interval:21.0pt">
                <div class="WordSection">
                    ${mhtml}
                    
                    <table id="hrdftrtbl" border="0" cellspacing="0" cellpadding="0">
                        <tbody>
                        <tr>
                                <td>
                                    ${header}
                                </td>
                                <td>
                                    ${footer}
                                </td>
                        </tr>
                        </tbody>
                    </table>
                </div>
                </body>
                </html>
                ${mhtmlBottom}
            `
        }
    })(jQuery);

    const MAX_PAGE = 'reviewList_maxPage';

    class Review {

        constructor() {
            this.panel = null;
            this.maxPage = GM_getValue(MAX_PAGE) || 100;
            this.powerLoading = false;
        }

        setMaxPage(val) {
            if(val) {
                this.maxPage = val;
                GM_setValue(MAX_PAGE, val);
            }
        }

        randomNumber() {
            return Math.ceil(Math.random()*100000000);
        }

        request(opts) {
            return new Promise(function(resolve, reject){
                opts.onload = function(response) {
                    if(response.status == 200){
                        resolve(response);
                    }else{
                        reject(response);
                    }
                };
                opts.onerror = function(err){
                    reject(err);
                };
                GM_xmlhttpRequest(opts);
            })
        }

        start() {
            this.createElementHtml();

            GM_registerMenuCommand("打开面板", () => {
                console.log('getReviews');
                this.panel.show();
            });
        }

        createElementHtml() {
            let _this = this;
            $(function(){
                let randomNumber = _this.randomNumber();
                let cssHtml = `
                    <style id="amz_review_css_${randomNumber}">
                        #amz_review_${randomNumber}{
                            position: fixed;
                            left: 0;
                            top: 0;
                            width: 100%;
                            height: 100%;
                            background-color: #fff;
                            opacity: 1;
                            z-index: 9999;
                            display: none;
                        }
                        
                        #amz_review_${randomNumber} .close {
                            cursor: pointer;
                        }
                        #amz_review_${randomNumber} .content {
                            margin: 30px auto 0;
                            max-width: 1000px;
                            width: 80%;
                        }
                        #amz_review_${randomNumber} .content textarea {
                            height: 300px
                        }
                        #amz_review_${randomNumber} .content .topBar {
                            margin-bottom: 20px;
                        }
                        #amz_review_${randomNumber} .content .topBar .item {
                            margin-right: 20px;
                        }
                        #amz_review_${randomNumber} .content .topBar .max {
                            width: 60px;
                        }
                        #amz_review_${randomNumber} .docx {
                            display: none;
                        }
                        #amz_review_${randomNumber} [disabled] {
                            background-color: #eee;
                        }
                    </style>
                `;
                let docxCss = $(`
                    <style>
                        .reviewAsin {
                            margin-top: 30px;
                            font-weight: bold;
                        }
                        .reviewItem {
                            margin-top: 20px;
                        }
                    </style>
                `);
                let updateExportCss = $(`
                    <style>
                        @page WordSection {
                            size: 1260px 500px;/*宽高*/
                            margin: 30px;/*边距*/
                            mso-header-margin: 20px;/*页眉距*/
                            mso-footer-margin: 20px;/*页脚距*/
                        }
                    </style>
                `);
                let html = $(`
                    <div id="amz_review_${randomNumber}">
                        <div class="content">
                            <div class="topBar">
                                <span class="item load">
                                    <button> 加载 </button>
                                </span>
                                <span class="item">
                                    最大加载页数：<input class="max" type="text" />
                                </span>
                                <span class="item self">
                                    <button> 本页 </button>
                                </span>
                                <span class="item close_">
                                    <button> 关闭 </button>
                                </span>
                            </div>
                            <textarea></textarea>
                            <div class="docx"></div>
                        </div>
                    </div>
                `);
                        
                let addLF = function(val, cb){
                    if( val.length > 0 && !val.endsWith('\n') ){
                        val += '\n';
                        cb && cb(val);
                    }
                    return val;
                };

                let textarea = html.find('textarea');
                textarea.on('input', function(){
                    addLF($(this).val(), val => $(this).val(val));
                });

                html.find('.close_').on('click', function(){
                    html.hide();
                });

                html.find('.self').on('click', function(){
                    textarea.val(addLF(textarea.val() + document.URL));
                });

                html.find('.max').val(_this.maxPage).on('change', function(){
                    _this.setMaxPage($(this).val());
                });

                let load = html.find('.load');

                let setLoadState = flag => {
                    _this.powerLoading = flag;
                    textarea.attr("disabled", flag)
                    load.find('button').text(flag ? ` 停止 ` : ' 加载 ');
                };

                load.on('click', function(){

                    if( _this.powerLoading ) {
                        setLoadState(false);
                        return;
                    }

                    let val = textarea.val();
                    if(val.length == 0) {
                        return;
                    }
                    
                    let asins = val.split('\n');
                    
                    let errs = [];
                    let infos = [];
            
                    for (let i = 0; i < asins.length; i++) {
                        let str = asins[i].trim();
                        if(str.length == 0) {
                            continue;
                        }
                        if( !/^[A-Za-z0-9]{10}$/.test(str) ){
                            let match = str.match(/^(https?:)?(\/\/)?([^\/]*?amazon[^\/]*?)\/([^\/]*?\/)*([A-Za-z0-9]{10})(\/|$)/);
                            if(match){
                                infos.push(match[5] + '|' + match[3]);
                            } else {
                                errs.push(str);
                            }
                        } else {
                            infos.push(str)
                        }
                    }
                    
                    if(errs.length > 0) {
                        alert(`以下输入无法解析出asin，请检查\n${errs.join('\n')}`);
                        return;
                    }

                    if(infos.length == 0) {
                        return;
                    }
                    
                    setLoadState(true);

                    _this.getData(infos).then(reviewMap => {
                        let str = '';
                        reviewMap.forEach(arr => {
                            let asin = arr[0], reviews = arr[1];
                            str += `<div class="reviewAsin">${asin}</div>`;
                            reviews.forEach(review => {
                                str += `<div class="reviewItem"><pre>${review}</pre></div>`;
                            });
                        });
                        html.find('.docx').html(str).wordExport({
                            fileName: "review", //文档名
                            fileType: ".docx", //文档类型  经测试  可以doc xls html 其他的自己去试
                            isHeader: true,//是否显示页眉  *xls  不要设置页眉页脚  改为false
                            isFooter: true,//是否显示页脚  *xls  不要设置页眉页脚  改为false
                            header: "测试页眉",//页眉标题
                            styles: docxCss, //要导出的内容style
                            updateExportCss: updateExportCss //需要修改导出的word样式  宽度高度 margin之类的
                        });

                    }).finally(v => {
                        setLoadState(false);
                    });
                });

                $("body").prepend(cssHtml).append(html);

                _this.panel = html;
            });
        }

        getData(asins){
            return asins.reduce((pre, next) => {
                return pre.then(reviewMap => {
                    let [asin, domain] = next.split('|');
                    return this.loadAllPage(asin, domain).then(reviews => {
                        reviewMap.push( [asin, reviews] );
                        return reviewMap;
                    });
                });
            }, Promise.resolve([]));
        }

        loadAllPage(asin, domain){
            let reviews = [];

            let iterate = (page) => {
                return this.loadPage(asin, page, domain).then(rt => {

                    if(rt.reviews && rt.reviews.length){
                        reviews = reviews.concat(rt.reviews);
                    }

                    if(rt.isEnd || page > this.maxPage) {
                        return reviews;
                    }
                    
                    return iterate(page + 1);
                });
            };

            return iterate(1);
        }

        loadPage( asin, page, domain ){
            domain = domain || 'www.amazon.com';
            return this.request({
                method: 'POST',
                url: `https://${domain}/hz/reviews-render/ajax/reviews/get/ref=cm_cr_arp_d_paging_btm_next_${ page }`,
                data: $.param({
                    pageNumber: page,
                    deviceType: 'desktop',
                    reftag: `cm_cr_arp_d_paging_btm_next_${page}`,
                    pageSize: 10,
                    asin: asin,
                    scope: 'reviewsAjax0',
                }),
                headers:  {
                    "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
                }
            }).then(res => {
                let isEnd = false;
                let reviews = [];

                if(!this.powerLoading){
                    return {isEnd: true, reviews};
                }

                let list = res.responseText.split("\x26\x26\x26");
                let global = 0;
                let pageNum = 0;
                for (let i = 0; i < list.length; i++) {
                    const str = list[i];
                    if (str.trim().length > 0) {
                        try {
                            let json = JSON.parse(str);
                            if(json[0] == 'append' && json[1] == '#cm_cr-review_list'){
                                let html = $(json[2]);
                                let content = html.find('.review-text-content');
                                if(content.length > 0){
                                    reviews.push(content.text().trim());
                                    pageNum++;
                                }else if(html.find('.a-disabled.a-last').length > 0){
                                    isEnd = true;
                                }
                            }else if(json[0] == 'update' && json[1] == '#filter-info-section'){
                                let match = json[2].match(/([\d,]+?) global reviews/i);
                                if(match) {
                                    global = match[1];
                                }
                            }
                        } catch (e) {
                            console.error(e, "Invalid streaming ajax JSON response: " + str);
                            isEnd = true;
                        }
                    }
                }

                this.panel.find('.load>button').text(` 停止 (${(page - 1)*10 + pageNum}/${global})) `)

                return {isEnd, reviews};
            });
        }
    }

    (new Review()).start();
    
})();